package com.keqiang.chart.impl.axis.impl.line

import android.graphics.Canvas
import android.graphics.DashPathEffect
import android.graphics.Paint
import android.graphics.PathMeasure
import android.text.TextPaint
import com.keqiang.chart.animation.Animator
import com.keqiang.chart.impl.axis.Axis
import com.keqiang.chart.impl.axis.AxisEntity
import com.keqiang.chart.impl.axis.render.BaseDataRender
import com.keqiang.chart.model.Entity
import com.keqiang.chart.utils.ObjectPool
import com.keqiang.chart.utils.px
import com.keqiang.utils.PaintUtils.getChartHeight

/**
 *
 * 折线图数据绘制器
 *
 * @author Created by wanggaowan on 2022/7/20 14:39
 */
open class LineChartDataRender(chart: LineChart) : BaseDataRender<LineChart>(chart) {

    // 记录临时float数据
    protected val mTempListF = mutableListOf<Float>()

    // 用于虚线的绘制
    protected var mDashPathEffect: DashPathEffect? = null

    override fun onDraw(canvas: Canvas, start: Float, top: Float, end: Float, bottom: Float,
                        gridWidth: Float, gridHeight: Float, animator: Animator?) {

        val tempPathMeasure = ObjectPool.obtainPathMeasure()
        chart.data?.forEach {
            if (it.values.size <= 1) {
                if (it.values.size == 1) {
                    getEntityPosition(it.values[0], start, gridWidth, top, bottom)
                }
            } else {
                when (it.lineStyle) {
                    LineData.CUBIC_BEZIER -> createCubicBezierPath(it, start, gridWidth, top, bottom, tempPathMeasure)

                    LineData.HORIZONTAL_BEZIER -> createHorizontalBezierPath(it, start, gridWidth, top, bottom, tempPathMeasure)

                    else -> createLinerPath(it, start, gridWidth, top, bottom, tempPathMeasure)
                }
            }

            drawLine(canvas, it, start, end, top, bottom, animator, tempPathMeasure)
            drawValueTextAndIndicator(canvas, it, top, bottom, animator, tempPathMeasure)
            drawAddedLine(canvas, it, start, end, top, bottom, animator?.getProgress() ?: 1f)
        }

        if ((animator?.getProgress() ?: 1f) == 1f) {
            chart.touchHandler?.touchData?.let {
                chart.highLightRender?.onDraw(canvas, start, top, end, bottom, it)
            }
        }
    }

    /**
     * 计算[Entity]在坐标系中的坐标位置
     */
    private fun getEntityPosition(
        entity: LineEntity,
        start: Float,
        gridWidth: Float,
        top: Float,
        bottom: Float
    ) {
        val x = start + gridWidth * entity.xIndex

        val yAxis = chart.yAxis
        val y = if (entity.isDraw) {
            bottom - ((entity.value - yAxis.min) / (yAxis.max - yAxis.min) * (bottom - top))
        } else {
            bottom
        }

        entity.drawRectF.set(x, y, x, y)
    }

    /**
     * 创建三次贝塞尔曲线平滑绘制路径
     */
    protected fun createCubicBezierPath(lineData: LineData, start: Float, gridWidth: Float, top: Float, bottom: Float, pathMeasure: PathMeasure) {
        val valuesPath = ObjectPool.obtainPath()
        mTempListF.clear()

        var isNewPath = true
        var lineStyle: Int? = null
        var prevDx: Float
        var prevDy: Float
        var curDx: Float
        var curDy: Float

        var prevPrev: LineEntity
        var cur: LineEntity = lineData.values[0]
        var prev: LineEntity = cur
        var next: LineEntity
        getEntityPosition(lineData.values[0], start, gridWidth, top, bottom)
        if (cur.isDraw) {
            lineStyle = cur.lineStyle

            valuesPath.moveTo(cur.drawRectF.left, cur.drawRectF.top)
            // 开始下标
            mTempListF.add(0f)
            // 开始的path length
            pathMeasure.setPath(valuesPath, false)
            mTempListF.add(pathMeasure.length)
            // 结束的path length，此处仅占位
            mTempListF.add(0f)

            isNewPath = false
        }

        for (index in 1 until lineData.values.size) {
            getEntityPosition(lineData.values[index], start, gridWidth, top, bottom)
            prevPrev = prev
            prev = cur
            cur = lineData.values[index]
            val nextIndex = if (index + 1 < lineData.values.size) index + 1 else index
            next = lineData.values[nextIndex]
            if (nextIndex != index) {
                getEntityPosition(lineData.values[nextIndex], start, gridWidth, top, bottom)
            }

            if (cur.isDraw) {
                if (!prev.isDraw) {
                    valuesPath.lineTo(cur.drawRectF.left, cur.drawRectF.top)
                    // 重新已此点作为起始点
                    prev = cur
                } else {
                    if (!next.isDraw) {
                        // 下个节点不绘制，那当前节点即为结束点
                        next = cur
                    }

                    prevDx =
                        (cur.drawRectF.left - prevPrev.drawRectF.left) * lineData.cubicIntensity
                    prevDy =
                        (getDrawY(cur, bottom) - getDrawY(prevPrev, bottom)) * lineData.cubicIntensity
                    curDx =
                        (next.drawRectF.left - prev.drawRectF.left) * lineData.cubicIntensity
                    curDy =
                        (getDrawY(next, bottom) - getDrawY(prev, bottom)) * lineData.cubicIntensity

                    valuesPath.cubicTo(prev.drawRectF.left + prevDx, getDrawY(prev, bottom) + prevDy,
                        cur.drawRectF.left - curDx, cur.drawRectF.top - curDy, cur.drawRectF.left, cur.drawRectF.top)
                }

                if (lineStyle != null && lineStyle != cur.lineStyle) {
                    if (mTempListF.size > 2) {
                        pathMeasure.setPath(valuesPath, false)
                        mTempListF[mTempListF.size - 1] = pathMeasure.length
                    }

                    isNewPath = true
                }

                if (isNewPath) {
                    // 开始下标
                    mTempListF.add(index * 1f)
                    // 开始的path length
                    pathMeasure.setPath(valuesPath, false)
                    mTempListF.add(pathMeasure.length)
                    // 结束的path length，此处仅占位
                    mTempListF.add(0f)

                    isNewPath = false
                }

                lineStyle = cur.lineStyle
            } else {
                if (mTempListF.size > 2 && !isNewPath) {
                    // !isNewPath表示当前节点前一个节点是需要绘制的
                    pathMeasure.setPath(valuesPath, false)
                    mTempListF[mTempListF.size - 1] = pathMeasure.length
                }

                valuesPath.lineTo(cur.drawRectF.left, cur.drawRectF.top)
                isNewPath = true
            }
        }

        if (mTempListF.isNotEmpty()) {
            pathMeasure.setPath(valuesPath, false)
            mTempListF[mTempListF.size - 1] = pathMeasure.length
        }

        ObjectPool.recyclePath(valuesPath)
    }

    /**
     * 创建三次贝塞尔曲线平滑绘制路径
     */
    protected fun createHorizontalBezierPath(lineData: LineData, start: Float, gridWidth: Float, top: Float, bottom: Float, pathMeasure: PathMeasure) {
        val valuesPath = ObjectPool.obtainPath()
        mTempListF.clear()

        var isNewPath = true
        var lineStyle: Int? = null

        var prev: LineEntity = lineData.values[0]
        var cur: LineEntity = prev
        getEntityPosition(lineData.values[0], start, gridWidth, top, bottom)
        if (cur.isDraw) {
            lineStyle = cur.lineStyle

            valuesPath.moveTo(cur.drawRectF.left, cur.drawRectF.top)
            // 开始下标
            mTempListF.add(0f)
            // 开始的path length
            pathMeasure.setPath(valuesPath, false)
            mTempListF.add(pathMeasure.length)
            // 结束的path length，此处仅占位
            mTempListF.add(0f)

            isNewPath = false
        }

        for (index in 1 until lineData.values.size) {
            getEntityPosition(lineData.values[index], start, gridWidth, top, bottom)
            prev = cur
            cur = lineData.values[index]
            if (cur.isDraw) {
                if (!prev.isDraw) {
                    valuesPath.lineTo(cur.drawRectF.left, cur.drawRectF.top)
                } else {
                    val cpx: Float =
                        prev.drawRectF.left + (cur.drawRectF.left - prev.drawRectF.left) / 2.0f
                    valuesPath.cubicTo(
                        cpx, getDrawY(prev, bottom),
                        cpx, getDrawY(cur, bottom),
                        cur.drawRectF.left, getDrawY(cur, bottom))
                }

                if (lineStyle != null && lineStyle != cur.lineStyle) {
                    if (mTempListF.size > 2) {
                        pathMeasure.setPath(valuesPath, false)
                        mTempListF[mTempListF.size - 1] = pathMeasure.length
                    }

                    isNewPath = true
                }

                if (isNewPath) {
                    // 开始下标
                    mTempListF.add(index * 1f)
                    // 开始的path length
                    pathMeasure.setPath(valuesPath, false)
                    mTempListF.add(pathMeasure.length)
                    // 结束的path length，此处仅占位
                    mTempListF.add(0f)

                    isNewPath = false
                }

                lineStyle = cur.lineStyle
            } else {
                if (mTempListF.size > 2 && !isNewPath) {
                    // !isNewPath表示当前节点前一个节点是需要绘制的
                    pathMeasure.setPath(valuesPath, false)
                    mTempListF[mTempListF.size - 1] = pathMeasure.length
                }

                valuesPath.lineTo(cur.drawRectF.left, cur.drawRectF.top)
                isNewPath = true
            }
        }

        if (mTempListF.isNotEmpty()) {
            pathMeasure.setPath(valuesPath, false)
            mTempListF[mTempListF.size - 1] = pathMeasure.length
        }

        ObjectPool.recyclePath(valuesPath)
    }

    private fun getDrawY(entity: AxisEntity, def: Float): Float {
        if (entity.isDraw) {
            return entity.drawRectF.top
        }
        return def
    }

    /**
     * 创建线性的折线图路径
     */
    protected fun createLinerPath(lineData: LineData, start: Float, gridWidth: Float, top: Float, bottom: Float, pathMeasure: PathMeasure) {
        val valuesPath = ObjectPool.obtainPath()
        mTempListF.clear()
        var lineStyle: Int? = null

        // 是否是新一段path路径
        var isNewPath = true
        for (index in lineData.values.indices) {
            val entity = lineData.values[index]
            getEntityPosition(entity, start, gridWidth, top, bottom)
            if (entity.isDraw) {
                if (valuesPath.isEmpty) {
                    valuesPath.moveTo(entity.drawRectF.left, entity.drawRectF.top)
                } else {
                    valuesPath.lineTo(entity.drawRectF.left, entity.drawRectF.top)
                }

                if (lineStyle != null && lineStyle != entity.lineStyle) {
                    if (mTempListF.size > 2) {
                        pathMeasure.setPath(valuesPath, false)
                        mTempListF[mTempListF.size - 1] = pathMeasure.length
                    }

                    isNewPath = true
                }

                if (isNewPath) {
                    // 开始下标
                    mTempListF.add(index * 1f)
                    // 开始的path length
                    pathMeasure.setPath(valuesPath, false)
                    mTempListF.add(pathMeasure.length)
                    // 结束的path length，此处仅占位
                    mTempListF.add(0f)

                    isNewPath = false
                }

                lineStyle = entity.lineStyle
            } else {
                if (mTempListF.size > 2 && !isNewPath) {
                    // !isNewPath表示当前节点前一个节点是需要绘制的
                    pathMeasure.setPath(valuesPath, false)
                    mTempListF[mTempListF.size - 1] = pathMeasure.length
                }

                if (valuesPath.isEmpty) {
                    valuesPath.moveTo(entity.drawRectF.left, entity.drawRectF.top)
                } else {
                    valuesPath.lineTo(entity.drawRectF.left, entity.drawRectF.top)
                }

                isNewPath = true
            }
        }

        if (mTempListF.isNotEmpty()) {
            pathMeasure.setPath(valuesPath, false)
            mTempListF[mTempListF.size - 1] = pathMeasure.length
        }

        ObjectPool.recyclePath(valuesPath)
    }

    /**
     * 绘制折线
     */
    protected open fun drawLine(canvas: Canvas, lineData: LineData, start: Float, end: Float, top: Float, bottom: Float, animator: Animator?, pathMeasure: PathMeasure) {
        if (mTempListF.isEmpty()) {
            return
        }

        val tempPath = ObjectPool.obtainPath()
        val paint = ObjectPool.obtainPaint<Paint>()

        canvas.save()
        if (!lineData.lineDrawCouldBeyondAxis) {
            canvas.clipRect(start, top - chart.yAxis.endPadding, end, bottom)
        }

        setPaint(paint, lineData.lineColor, lineData.lineWidth)
        paint.shader = lineData.lineShaderFactory?.invoke(start, top, end, bottom)
        for (index in 0 until mTempListF.size / 3) {
            val valueIndex = mTempListF[index * 3].toInt()
            val startLength = mTempListF[index * 3 + 1]
            val endLength = mTempListF[index * 3 + 2]
            if (endLength <= startLength) {
                continue
            }

            val entity = lineData.values[valueIndex]
            setPathEffect(entity.lineStyle, paint)
            if (animator == null || !animator.isStart()) {
                if (lineData.fillBg) {
                    drawFillBg(canvas, pathMeasure, startLength, endLength, lineData, start, end, top, bottom)
                }

                tempPath.reset()
                pathMeasure.getSegment(startLength, endLength, tempPath, true)
                canvas.drawPath(tempPath, paint)
                continue
            }

            val animLength = pathMeasure.length * animator.getProgress()
            if (endLength < animLength) {
                if (lineData.fillBg) {
                    drawFillBg(canvas, pathMeasure, startLength, endLength, lineData, start, end, top, bottom)
                }

                tempPath.reset()
                pathMeasure.getSegment(startLength, endLength, tempPath, true)
                canvas.drawPath(tempPath, paint)
            } else if (startLength < animLength) {
                if (lineData.fillBg) {
                    drawFillBg(canvas, pathMeasure, startLength, animLength, lineData, start, end, top, bottom)
                }

                tempPath.reset()
                pathMeasure.getSegment(startLength, animLength, tempPath, true)
                canvas.drawPath(tempPath, paint)
            }
        }

        canvas.restore()

        ObjectPool.recyclePath(tempPath)
        ObjectPool.recyclePaint(paint)
    }

    /**
     * 绘制折线背景填充色
     */
    protected open fun drawFillBg(canvas: Canvas, pathMeasure: PathMeasure, startLength: Float, endLength: Float,
                                  lineData: LineData, start: Float, end: Float, top: Float, bottom: Float) {
        val tempPath = ObjectPool.obtainPath()
        val tempFloatArray = ObjectPool.obtainFloatArray()

        tempPath.reset()
        pathMeasure.getSegment(startLength, endLength, tempPath, true)
        pathMeasure.getPosTan(endLength, tempFloatArray, null)
        tempPath.lineTo(tempFloatArray[0], bottom)
        pathMeasure.getPosTan(startLength, tempFloatArray, null)
        tempPath.lineTo(tempFloatArray[0], bottom)
        tempPath.close()

        if (lineData.fillBgDrawable != null) {
            lineData.fillBgDrawable?.let {
                if (lineData.lineStyle == LineData.CUBIC_BEZIER) {
                    // 如果绘制的线条是贝塞尔曲线，会超出top值，因此图片的顶部为轴线的顶部
                    it.setBounds(start.toInt(), (top - chart.yAxis.endPadding).toInt(), end.toInt(), bottom.toInt())
                } else {
                    it.setBounds(start.toInt(), top.toInt(), end.toInt(), bottom.toInt())
                }

                canvas.save()
                canvas.clipPath(tempPath)
                it.draw(canvas)
                canvas.restore()
            }
        } else if (lineData.fillBgColor != 0) {
            canvas.save()
            canvas.clipPath(tempPath)
            canvas.drawColor(lineData.fillBgColor)
            canvas.restore()
        }

        ObjectPool.recyclePath(tempPath)
        ObjectPool.recycleFloatArray(tempFloatArray)
    }

    /**
     * 绘制文本值和数值指示器
     */
    protected open fun drawValueTextAndIndicator(canvas: Canvas, lineData: LineData, top: Float, bottom: Float, animator: Animator?, pathMeasure: PathMeasure) {
        val tempFloatArray = ObjectPool.obtainFloatArray()
        val textPaint = ObjectPool.obtainPaint<TextPaint>(true)

        if (animator != null && animator.isStart()) {
            pathMeasure.getPosTan(animator.getProgress() * pathMeasure.length, tempFloatArray, null)
        }

        for (index in lineData.values.indices) {
            val entity = lineData.values[index]
            if (entity.isDraw) {
                if (animator != null && animator.isStart() && tempFloatArray[0] < entity.drawRectF.left) {
                    break
                }

                var indicatorOffset = entity.labelOffset
                if (entity.drawIndicator && entity.indicatorDrawable != null
                    && entity.indicatorSize > 0) {
                    val half = entity.indicatorSize / 2f
                    entity.indicatorDrawable?.setBounds((entity.drawRectF.left - half).toInt(), (entity.drawRectF.top - half).toInt(),
                        (entity.drawRectF.left + half).toInt(), (entity.drawRectF.top + half).toInt())
                    entity.indicatorDrawable?.draw(canvas)
                    indicatorOffset += half
                }

                val label = entity.label
                if (entity.drawLabel && entity.labelTextSize > 0 && !label.isNullOrEmpty()) {
                    textPaint.textSize = entity.labelTextSize
                    textPaint.color = entity.labelColor

                    val gravity = entity.labelGravity
                    val isCenter = gravity == Gravity.CENTER
                    val isEnd = gravity == Gravity.END

                    textPaint.textAlign = when {
                        isCenter -> {
                            Paint.Align.CENTER
                        }

                        isEnd -> {
                            Paint.Align.RIGHT
                        }

                        else -> {
                            Paint.Align.LEFT
                        }
                    }

                    var y = entity.drawRectF.top - indicatorOffset
                    val textHeight = textPaint.getChartHeight(includePadding = false)
                    if (y - textHeight < top) {
                        y = entity.drawRectF.top + indicatorOffset + textHeight
                    }

                    canvas.drawText(label, entity.drawRectF.left, y, textPaint)
                }
            }
        }

        ObjectPool.recycleFloatArray(tempFloatArray)
        ObjectPool.recyclePaint(textPaint)
    }

    protected open fun setPaint(paint: Paint, color: Int, strokeWidth: Float = 0f) {
        paint.color = color
        paint.strokeWidth = strokeWidth
        paint.style = Paint.Style.STROKE
    }

    protected open fun setPathEffect(lineStyle: Int?, paint: Paint) {
        if (lineStyle == LineStyle.DOTTED_LINE) {
            if (mDashPathEffect == null) {
                mDashPathEffect = DashPathEffect(floatArrayOf(10f.px, 10f.px), 0f)
            }
            paint.pathEffect = mDashPathEffect
        } else {
            paint.pathEffect = null
        }
    }

    /**
     * [end]： 折线图X轴绘制区域结束点，不包括[Axis.endPadding]
     *
     * [top]： 折线图Y轴绘制区域起始点，不包括[Axis.endPadding]
     *
     * [animProgress]:以动画形式绘制，指定动画进度
     */
    protected open fun drawAddedLine(
        canvas: Canvas, lineData: LineData,
        start: Float, end: Float, top: Float,
        bottom: Float, animProgress: Float = 1f
    ) {

        val tempRectF = ObjectPool.obtainRectF()
        val tempPath = ObjectPool.obtainPath()
        val textPaint = ObjectPool.obtainPaint<TextPaint>(true)

        canvas.save()
        tempRectF.set(start, top - chart.yAxis.endPadding, end, bottom)
        canvas.clipRect(tempRectF)

        val yAxis = chart.yAxis
        val xAxis = chart.xAxis
        lineData.addedLines?.forEach {
            val y = bottom - ((it.value - yAxis.min) / (yAxis.max - yAxis.min) * (bottom - top))
            val stopX = if (it.useEndPadding) end else end + xAxis.endPadding
            val startX = if (it.useStartPadding) start else start - xAxis.endPadding
            setPaint(textPaint, it.lineColor, it.lineWidth)
            textPaint.pathEffect = it.pathEffect
            tempPath.reset()
            tempPath.moveTo(startX, y)
            tempPath.lineTo(stopX * animProgress, y)
            canvas.drawPath(tempPath, textPaint)

            if (animProgress < 1f || it.label.isNullOrEmpty() || it.labelTextSize <= 0) {
                return@forEach
            }

            val isHorizontalCenter =
                it.labelGravity and android.view.Gravity.LEFT != android.view.Gravity.LEFT
                    && it.labelGravity and android.view.Gravity.RIGHT != android.view.Gravity.RIGHT
                    && it.labelGravity and android.view.Gravity.CENTER_HORIZONTAL == android.view.Gravity.CENTER_HORIZONTAL
            val isEnd =
                !isHorizontalCenter && (it.labelGravity and android.view.Gravity.RIGHT) == android.view.Gravity.RIGHT
            val isBottom =
                (it.labelGravity and android.view.Gravity.BOTTOM) == android.view.Gravity.BOTTOM

            textPaint.style = Paint.Style.FILL
            textPaint.pathEffect = null
            textPaint.color = it.labelTextColor
            textPaint.textSize = it.labelTextSize
            var textStartX: Float = it.labelXOffset
            textPaint.textAlign = when {
                isHorizontalCenter -> {
                    textStartX += startX + (stopX - startX) / 2f
                    Paint.Align.CENTER
                }

                isEnd -> {
                    textStartX += stopX
                    Paint.Align.RIGHT
                }

                else -> {
                    textStartX += startX
                    Paint.Align.LEFT
                }
            }

            val fm = textPaint.fontMetrics
            val drawTop: Float
            val drawBottom: Float
            if (isBottom) {
                drawTop = y + it.lineWidth / 2f
                drawBottom = drawTop + fm.descent - fm.ascent
            } else {
                drawBottom = y - it.lineWidth / 2f
                drawTop = drawBottom - (fm.descent - fm.ascent)
            }

            it.drawRectF.set(0f, drawTop, 0f, drawBottom)
            val textStartY = if (isBottom) {
                it.drawRectF.top - fm.ascent + it.labelYOffset
            } else {
                it.drawRectF.bottom - fm.descent - it.labelYOffset
            }

            textPaint.textSize = it.labelTextSize
            canvas.drawText(it.label!!, textStartX, textStartY, textPaint)
        }

        canvas.restore()

        ObjectPool.recycleRectF(tempRectF)
        ObjectPool.recyclePath(tempPath)
        ObjectPool.recyclePaint(textPaint)
    }
}